package com.realshare.frame.util.safety.america;

import com.realshare.frame.util.FileUtils;
import com.realshare.frame.util.safety.Base64Utils;
import com.realshare.frame.util.safety.FileHandler;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Date;

/**
 * Created by 曹传喜 on 2016/10/27.
 * <p>
 * AES  对称加密解密器 听说跨平台，涵盖Windows,Android,Mac,Linux。。
 * </p>
 * <p>
 * 你这个安全数据传输可以改进一下，可以参照 SSL/TLS 协议握手的原理，AES 密钥不是固定的，由通信双方各随机生成一半，
 * 双方生成的一半密钥通过对方的 RSA 公钥加密传送给对方，对方使用自己的 RSA 私钥解密，
 * 这样与自己的另外一半 AES 密钥拼合成数据传输的加密密钥，从而完成密钥的交换过程。
 * </p>
 * <p>
 * 这一块是用于移动客户端，架构上只要保证客户端的数据不被窃取即可。 客户端的AES密钥是随机生成，
 * RSA加密不用于加密数据，而是用来加密AES的密钥。 因为客户端不是支付类的客户端，所以没有对每台终端生成独立的私钥证书。
 * 另外，之前客户端与服务端的交互是短连接，每次交互都随机生成AES密钥，数据用AES加密，AES密钥用RSA加密，
 * 每次请求同时传递密文和加密后的AES密钥； 现在的方案是，客户端启动后，先进行一次检查公钥证书的交互，再进行一次激活交互，
 * 激活交互用于客户端传递AES密钥（128位，使用RSA公钥加密），服务端返回激活流水，后续的交互，客户端使用激活时设定的AES密钥进行数据加密。
 * -------------------------------------------------------------------------------------------------------------------------------------------------------------
 * <p/>
 * <p>
 * AES与3DES的比较：
 * 算法名称		算法类型			密钥长度	         速度	解密时间（建设机器每秒尝试255个密钥）	资源消耗
 * AES		    对称block密码		128、192、256位		 高		1490000亿年						        低
 * 3DES		    对称feistel密码	    112位或168位		 低		46亿年		                            中
 * </p>
 */

public class AESUtils  {
    private static final String PADDING = "AES/CBC/PKCS5Padding";
    private static final String ALGORITHM = "AES";
    private static final int KEY_SIZE = 128;

    /**
     * 生成AES对称秘钥，并对AES对称秘钥进行base64编码
     *
     * @return 返回密钥
     */
    public static String generateKey() {
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
            SecureRandom secureRandom = new SecureRandom();
            keyGenerator.init(KEY_SIZE, secureRandom);
            SecretKey aesKey = keyGenerator.generateKey();
            return Base64Utils.encoder(aesKey.getEncoded());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return "";
    }

    private static Cipher getCipher(int mode, String key) {
        Cipher mCipher = null;
        try {
            int keySizeAES128 = 16;
            byte[] keyPtr = new byte[keySizeAES128];
            IvParameterSpec ivParam = new IvParameterSpec(keyPtr);
            byte[] passPtr = key.getBytes();
            mCipher = Cipher.getInstance(PADDING);
            for (int i = 0; i < keySizeAES128; i++) {
                keyPtr[i] = i < passPtr.length ? passPtr[i] : 0;
            }
            SecretKeySpec keySpec = new SecretKeySpec(keyPtr, ALGORITHM);
            mCipher.init(mode, keySpec, ivParam);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return mCipher;
    }

    /**
     * AES 加密 拥有跨语言特性 C#, Java, PHP, Python和Javascript
     *
     * @param data 待加密的明文
     * @param key  加密的key 长度16位字符串
     * @return base64编码的密文
     */
    public static String encrypt(String data, String key) {

        try {
            Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, key);
            int blockSize = cipher.getBlockSize();
            byte[] dataBytes = data.getBytes();
            int plaintextLength = dataBytes.length;
            if (plaintextLength % blockSize != 0) {
                plaintextLength = plaintextLength + (blockSize - (plaintextLength % blockSize));
            }
            byte[] plaintext = new byte[plaintextLength];
            System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);

            byte[] encrypted = cipher.doFinal(plaintext);
            if (encrypted.length > 0) {
                return Base64Utils.encoder(encrypted);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * AES 解密 拥有跨语言特性 C#, Java, PHP, Python和Javascript
     *
     * @param data 待解密的密文
     * @param key  解密的key 长度16位字符串
     * @return 解密后的明文
     */
    public static String decrypt(String data, String key) {
        try {
            byte[] dataBytes = Base64Utils.decoder(data);
            byte[] original = getCipher(Cipher.DECRYPT_MODE, key).doFinal(dataBytes);
            if (original.length > 0) {
                return new String(original).replaceAll("\0", "");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    /**
     * 加密文件
     *
     * @param sourcePath  源文件
     * @param encryptPath 加密文件
     * @param key         密钥
     */
    public static void encryptFile(String sourcePath, String encryptPath, String key) {
        Cipher enCipher = getCipher(Cipher.ENCRYPT_MODE, key);
        FileHandler.handlerFile(encryptPath, sourcePath, enCipher);
    }

    /**
     * 解密文件
     *
     * @param encryptPath 加密文件
     * @param decryptPath 解密文件
     * @param key         密钥
     */
    public static void decryptFile(String encryptPath, String decryptPath, String key) {
        Cipher deCipher = getCipher(Cipher.DECRYPT_MODE, key);
        FileHandler.handlerFile(decryptPath, encryptPath, deCipher);
    }

    public static void main(String[] args) {
        String key = generateKey();
        String source = "一醉汉不慎从三楼掉下，引来路人围观，一警察过来：发生了什么事？醉汉：不清楚，我也是刚到！";
        Date date = new Date();
        testFile(source, key);
        test(source, key);
        System.out.println("加密耗时：" + (new Date().getTime() - date.getTime()) + "毫秒");
    }

    private static void testFile(String source, String key) {
        String sourcePath = "D:\\1.txt";
        String encryptPath = "D:\\aes_encrypt.txt";
        String decryptPath = "D:\\aes_decrypt.txt";
        FileUtils.writeFile(new File(sourcePath), source);
        encryptFile(sourcePath, encryptPath, key);
        decryptFile(encryptPath, decryptPath, key);
    }

    private static void test(String source, String key) {
        System.out.println("原文字：" + source);
        System.out.println("密钥：" + key);
        String encryptStr = encrypt(source, key);
        System.out.println("aes加密后：" + encryptStr);
        String decryptStr = decrypt(encryptStr, key);
        System.out.println("aes解密后：" + decryptStr);
    }
}
